﻿Imports System
Imports System.Collections.Generic
Imports System.Diagnostics
Imports System.Runtime.InteropServices
Imports System.Workflow.ComponentModel
Imports System.Workflow.Runtime.Tracking

Namespace System.Workflow.Runtime
    Friend Class TrackingProfileManager
        ' Methods
        Friend Sub New()
        End Sub

        Private Function AddToCache(ByVal profile As RTTrackingProfile, ByVal serviceType As Type) As Boolean
            Return Me.AddToCache(profile, serviceType, False)
        End Function

        Private Function AddToCache(ByVal profile As RTTrackingProfile, ByVal serviceType As Type, ByVal resetNoProfiles As Boolean) As Boolean
            If (serviceType Is Nothing) Then
                Return False
            End If
            SyncLock Me._cacheLock
                Dim dictionary As Dictionary([Of] Type, ProfileList) = Nothing
                If Not Me._cacheLookup.TryGetValue(serviceType, dictionary) Then
                    dictionary = New Dictionary([Of] Type, ProfileList)
                    Me._cacheLookup.Add(serviceType, dictionary)
                End If
                Dim list As ProfileList = Nothing
                If Not dictionary.TryGetValue(profile.WorkflowType, list) Then
                    list = New ProfileList
                    dictionary.Add(profile.WorkflowType, list)
                End If
                If resetNoProfiles Then
                    list.NoProfile = False
                End If
                Return list.Profiles.TryAdd(New CacheItem(profile))
            End SyncLock
        End Function

        Public Shared Sub ClearCache()
            WorkflowRuntime.ClearTrackingProfileCache
        End Sub

        Friend Sub ClearCacheImpl()
            SyncLock Me._cacheLock
                Me._cacheLookup = New Dictionary([Of] Type, Dictionary([Of] Type, ProfileList))
            End SyncLock
        End Sub

        Private Function CreateProfile(ByVal profile As TrackingProfile, ByVal workflowType As Type, ByVal serviceType As Type) As RTTrackingProfile
            Return New RTTrackingProfile(profile, Me._runtime.GetWorkflowDefinition(workflowType), serviceType)
        End Function

        Private Function CreateProfile(ByVal profile As TrackingProfile, ByVal schedule As Activity, ByVal serviceType As Type) As RTTrackingProfile
            Return New RTTrackingProfile(profile, schedule, serviceType)
        End Function

        Friend Function GetProfile(ByVal service As TrackingService, ByVal schedule As Activity) As RTTrackingProfile
            If Not Me._init Then
                Throw New ApplicationException(ExecutionStringManager.TrackingProfileManagerNotInitialized)
            End If
            If ((service Is Nothing) OrElse (schedule Is Nothing)) Then
                WorkflowTrace.Tracking.TraceEvent(TraceEventType.Error, 0, ExecutionStringManager.NullParameters)
                Return Nothing
            End If
            Dim workflowType As Type = schedule.GetType
            Dim profile As RTTrackingProfile = Nothing
            If (TypeOf service Is IProfileNotification AndAlso Me.TryGetFromCache(service.GetType, workflowType, profile)) Then
                Return profile
            End If
            Dim profile2 As TrackingProfile = Nothing
            If Not service.TryGetProfile(workflowType, profile2) Then
                Me.RemoveProfile(workflowType, service.GetType)
                Return Nothing
            End If
            If Me.TryGetFromCache(service.GetType, workflowType, profile2.Version, profile) Then
                Return profile
            End If
            Dim str As String = [TryCast](schedule.GetValue(Activity.WorkflowXamlMarkupProperty),String)
            If ((Not str Is Nothing) AndAlso (str.Length > 0)) Then
                Return Me.CreateProfile(profile2, schedule, service.GetType)
            End If
            profile = Me.CreateProfile(profile2, workflowType, service.GetType)
            SyncLock Me._cacheLock
                Dim profile3 As RTTrackingProfile = Nothing
                If Me.TryGetFromCache(service.GetType, workflowType, profile2.Version, profile3) Then
                    Return profile3
                End If
                If Not Me.AddToCache(profile, service.GetType) Then
                    Throw New ApplicationException(ExecutionStringManager.ProfileCacheInsertFailure)
                End If
                Return profile
            End SyncLock
        End Function

        Friend Function GetProfile(ByVal service As TrackingService, ByVal workflow As Activity, ByVal instanceId As Guid) As RTTrackingProfile
            Dim profile As TrackingProfile = service.GetProfile(instanceId)
            If (profile Is Nothing) Then
                Return Nothing
            End If
            Return New RTTrackingProfile(profile, workflow, service.GetType)
        End Function

        Friend Function GetProfile(ByVal service As TrackingService, ByVal workflow As Activity, ByVal versionId As Version) As RTTrackingProfile
            If (service Is Nothing) Then
                Throw New ArgumentNullException("service")
            End If
            If (workflow Is Nothing) Then
                Throw New ArgumentNullException("workflow")
            End If
            If Not Me._init Then
                Throw New InvalidOperationException(ExecutionStringManager.TrackingProfileManagerNotInitialized)
            End If
            Dim workflowType As Type = workflow.GetType
            Dim profile As RTTrackingProfile = Nothing
            If Me.TryGetFromCache(service.GetType, workflowType, versionId, profile) Then
                Return profile
            End If
            Dim profile2 As TrackingProfile = service.GetProfile(workflowType, versionId)
            Dim str As String = [TryCast](workflow.GetValue(Activity.WorkflowXamlMarkupProperty),String)
            If ((Not str Is Nothing) AndAlso (str.Length > 0)) Then
                Return Me.CreateProfile(profile2, workflow, service.GetType)
            End If
            profile = Me.CreateProfile(profile2, workflowType, service.GetType)
            SyncLock Me._cacheLock
                Dim profile3 As RTTrackingProfile = Nothing
                If Me.TryGetFromCache(service.GetType, workflowType, versionId, profile3) Then
                    Return profile3
                End If
                If Not Me.AddToCache(profile, service.GetType) Then
                    Throw New ApplicationException(ExecutionStringManager.ProfileCacheInsertFailure)
                End If
                Return profile
            End SyncLock
        End Function

        Friend Sub Initialize(ByVal runtime As WorkflowRuntime)
            SyncLock Me._cacheLock
                If (runtime Is Nothing) Then
                    Throw New ArgumentException(ExecutionStringManager.NullEngine)
                End If
                Me._runtime = runtime
                Me._cacheLookup = New Dictionary([Of] Type, Dictionary([Of] Type, ProfileList))
                If (Not runtime.TrackingServices Is Nothing) Then
                    Me._services = runtime.TrackingServices
                    Dim service As TrackingService
                    For Each service In Me._services
                        If TypeOf service Is IProfileNotification Then
                            AddHandler DirectCast(service, IProfileNotification).ProfileUpdated, New EventHandler([Of] ProfileUpdatedEventArgs)(AddressOf Me.ProfileUpdated)
                            AddHandler DirectCast(service, IProfileNotification).ProfileRemoved, New EventHandler([Of] ProfileRemovedEventArgs)(AddressOf Me.ProfileRemoved)
                        End If
                    Next
                End If
                Me._init = True
            End SyncLock
        End Sub

        Private Sub ProfileRemoved(ByVal sender As Object, ByVal e As ProfileRemovedEventArgs)
            If (sender Is Nothing) Then
                Throw New ArgumentNullException("sender")
            End If
            If (e.WorkflowType Is Nothing) Then
                Throw New ArgumentNullException("e")
            End If
            Me.RemoveProfile(e.WorkflowType, sender.GetType)
        End Sub

        Private Sub ProfileUpdated(ByVal sender As Object, ByVal e As ProfileUpdatedEventArgs)
            If (sender Is Nothing) Then
                Throw New ArgumentNullException("sender")
            End If
            Dim serviceType As Type = sender.GetType
            If (e.WorkflowType Is Nothing) Then
                Throw New ArgumentNullException("e")
            End If
            If (e.TrackingProfile Is Nothing) Then
                Me.RemoveProfile(e.WorkflowType, serviceType)
            Else
                Dim profile As RTTrackingProfile = Me.CreateProfile(e.TrackingProfile, e.WorkflowType, serviceType)
                Me.AddToCache(profile, serviceType, True)
            End If
        End Sub

        Private Sub RemoveProfile(ByVal workflowType As Type, ByVal serviceType As Type)
            SyncLock Me._cacheLock
                Dim dictionary As Dictionary([Of] Type, ProfileList) = Nothing
                If Not Me._cacheLookup.TryGetValue(serviceType, dictionary) Then
                    dictionary = New Dictionary([Of] Type, ProfileList)
                    Me._cacheLookup.Add(serviceType, dictionary)
                End If
                Dim list As ProfileList = Nothing
                If Not dictionary.TryGetValue(workflowType, list) Then
                    list = New ProfileList
                    dictionary.Add(workflowType, list)
                End If
                list.NoProfile = True
            End SyncLock
        End Sub

        Private Function TryGetFromCache(ByVal serviceType As Type, ByVal workflowType As Type, <Out> ByRef profile As RTTrackingProfile) As Boolean
            Return Me.TryGetFromCache(serviceType, workflowType, New Version(0, 0), profile)
        End Function

        Private Function TryGetFromCache(ByVal serviceType As Type, ByVal workflowType As Type, ByVal versionId As Version, <Out> ByRef profile As RTTrackingProfile) As Boolean
            profile = Nothing
            Dim item As CacheItem = Nothing
            SyncLock Me._cacheLock
                Dim dictionary As Dictionary([Of] Type, ProfileList) = Nothing
                If Me._cacheLookup.TryGetValue(serviceType, dictionary) Then
                    Dim list As ProfileList = Nothing
                    If Not dictionary.TryGetValue(workflowType, list) Then
                        Return False
                    End If
                    If (versionId.Major = 0) Then
                        If Not list.NoProfile Then
                            If ((list.Profiles Is Nothing) OrElse (list.Profiles.Count = 0)) Then
                                Return False
                            End If
                            Dim num As Integer = (list.Profiles.Count - 1)
                            If (list.Profiles.Item(num) Is Nothing) Then
                                Return False
                            End If
                            profile = list.Profiles.Item(num).TrackingProfile
                        End If
                        Return True
                    End If
                    If (((Not list.Profiles Is Nothing) AndAlso (list.Profiles.Count <> 0)) AndAlso list.Profiles.TryGetValue(New CacheItem(workflowType, versionId), item)) Then
                        profile = item.TrackingProfile
                        Return True
                    End If
                End If
                Return False
            End SyncLock
        End Function

        Friend Sub Uninitialize()
            SyncLock Me._cacheLock
                If (Not Me._runtime Is Nothing) Then
                    Dim service As TrackingService
                    For Each service In Me._services
                        If TypeOf service Is IProfileNotification Then
                            RemoveHandler DirectCast(service, IProfileNotification).ProfileUpdated, New EventHandler([Of] ProfileUpdatedEventArgs)(AddressOf Me.ProfileUpdated)
                            RemoveHandler DirectCast(service, IProfileNotification).ProfileRemoved, New EventHandler([Of] ProfileRemovedEventArgs)(AddressOf Me.ProfileRemoved)
                        End If
                    Next
                End If
                Me._runtime = Nothing
                Me._services = Nothing
                Me._init = False
            End SyncLock
        End Sub


        ' Fields
        Private _cacheLock As Object = New Object
        Private _cacheLookup As Dictionary([Of] Type, Dictionary([Of] Type, ProfileList))
        Private _init As Boolean
        Private _runtime As WorkflowRuntime
        Private _services As List([Of] TrackingService)

        ' Nested Types
        Private Class CacheItem
            Implements IComparable
            ' Methods
            Friend Sub New()
                Me.LastAccess = DateTime.UtcNow
                Me.VersionId = New Version(0, 0)
            End Sub

            Friend Sub New(ByVal profile As RTTrackingProfile)
                Me.LastAccess = DateTime.UtcNow
                Me.VersionId = New Version(0, 0)
                If (profile Is Nothing) Then
                    Throw New ArgumentNullException("profile")
                End If
                Me.ScheduleType = profile.WorkflowType
                Me.TrackingProfile = profile
                Me.VersionId = profile.Version
            End Sub

            Friend Sub New(ByVal workflowType As Type, ByVal versionId As Version)
                Me.LastAccess = DateTime.UtcNow
                Me.VersionId = New Version(0, 0)
                Me.VersionId = versionId
                Me.ScheduleType = workflowType
            End Sub

            Public Function CompareTo(ByVal obj As Object) As Integer
                If Not TypeOf obj Is CacheItem Then
                    Throw New ArgumentException(ExecutionStringManager.InvalidCacheItem)
                End If
                Dim item As CacheItem = DirectCast(obj, CacheItem)
                If ((Me.VersionId Is item.VersionId) AndAlso (Me.ScheduleType Is item.ScheduleType)) Then
                    Return 0
                End If
                If (Me.VersionId <= item.VersionId) Then
                    Return -1
                End If
                Return 1
            End Function


            ' Fields
            Friend LastAccess As DateTime
            Friend ScheduleType As Type
            Friend TrackingProfile As RTTrackingProfile
            Friend VersionId As Version
        End Class

        Private Class ProfileList
            ' Fields
            Friend NoProfile As Boolean
            Friend Profiles As Set([Of] CacheItem) = New Set([Of] CacheItem)(5)
        End Class
    End Class
End Namespace

